package net.xdclass.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.enums.CouponCategoryEnum;
import net.xdclass.enums.CouponPublishEnum;
import net.xdclass.enums.CouponStateEnum;
import net.xdclass.exception.BizException;
import net.xdclass.intercepto.LoginInterceptor;
import net.xdclass.mapper.CouponRecordMapper;
import net.xdclass.model.CouponDO;
import net.xdclass.mapper.CouponMapper;
import net.xdclass.model.CouponRecordDO;
import net.xdclass.model.LoginUser;
import net.xdclass.request.NewUserCouponRequest;
import net.xdclass.service.CouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.xdclass.utils.CommonUtil;
import net.xdclass.utils.JsonData;
import net.xdclass.vo.CouponVO;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author cx
 * @since 2023-07-11
 */
@Service
@Slf4j
public class CouponServiceImpl extends ServiceImpl<CouponMapper, CouponDO> implements CouponService {
    @Autowired
    private CouponMapper couponMapper;
    @Autowired
    private CouponRecordMapper couponRecordMapper;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public Map<String, Object> pageCouponActivity(int page, int size) {

        Page<CouponDO> pageInfo = new Page<>(page, size);

        IPage<CouponDO> couponDOIPage = couponMapper.selectPage(pageInfo, new QueryWrapper<CouponDO>()
                .eq("publish", CouponPublishEnum.PUBLISH)
                .eq("category", CouponCategoryEnum.PROMOTION)
                .orderByDesc("create_time"));


        Map<String, Object> pageMap = new HashMap<>(3);
        //总条数
        pageMap.put("total_record", couponDOIPage.getTotal());
        //总页数
        pageMap.put("total_page", couponDOIPage.getPages());

        pageMap.put("current_data", couponDOIPage.getRecords().stream().map(obj -> beanProcess(obj)).collect(Collectors.toList()));


        return pageMap;
    }

    @Override
    public JsonData addCounpon(Long couponId, CouponCategoryEnum promotion) {
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        //当前线程
        String uuid = CommonUtil.generateUUID();
        //细化粒度
        String lockKey = "lock:coupon" + couponId;
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        log.info("加锁成功");
        //Duration.ofMillis(30)  设置时间防止死锁
        //加锁成功
            try {
                //业务逻辑
                CouponDO couponDO = couponMapper.selectOne(new QueryWrapper<CouponDO>()
                        .eq("id", couponId)
                        .eq("category", promotion.name())
                        .eq("publish", CouponPublishEnum.PUBLISH)
                );
                if (couponDO == null) {
                    throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
                }
                //优惠券是否能领取
                this.checkCoupon(couponDO, loginUser.getId());
                //构建领券记录
                CouponRecordDO couponRecordDO = new CouponRecordDO();
                BeanUtils.copyProperties(couponDO, couponRecordDO);
                couponRecordDO.setCreateTime(new Date());
                couponRecordDO.setUseState(CouponStateEnum.NEW.name());
                couponRecordDO.setUserId(loginUser.getId());
                couponRecordDO.setUserName(loginUser.getName());
                couponRecordDO.setCouponId(couponId);
                couponRecordDO.setId(null);
                //扣减库存
                int rows = couponMapper.reduceStock(couponId);
//        int rows =1;

                //int flag = 1/0;

                if (rows == 1) {
                    //库存扣减成功才保存记录
                    couponRecordMapper.insert(couponRecordDO);

                } else {
                    log.warn("发放优惠券失败:{},用户:{}", couponDO, loginUser);

                    throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
                }
            }finally {
                //解锁
                //KEYS[1]) 传过去的值等同 lockKey
                // ARGV[1] 就是uuid、
                lock.unlock();
                log.info("解锁");
            }

        return JsonData.buildSuccess();
    }

    /**
     * 本地直接调用发送优惠券方法,需要构造一个用户存在threadlocal
     * @param newUserCouponRequest
     * @return
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED)
    @Override
    public JsonData initNewUserCoupon(NewUserCouponRequest newUserCouponRequest) {
        LoginUser loginUser = new LoginUser();
        loginUser.setId(newUserCouponRequest.getUserId());
        loginUser.setName(newUserCouponRequest.getName());
        LoginInterceptor.threadLocal.set(loginUser);
        //查询新用户有哪些优惠券
      List<CouponDO> couponDOS = couponMapper.selectList(new QueryWrapper<CouponDO>().
              eq("category",CouponCategoryEnum.NEW_USER.name()));

        for (CouponDO couponDO : couponDOS) {
            //幂等操作，调用需要加锁
            this.addCounpon(couponDO.getId(),CouponCategoryEnum.NEW_USER);
        }

        return JsonData.buildSuccess();
    }

    /**
     * 校验是否能领取
     *
     * @param couponDO
     * @param userId
     */
    private void checkCoupon(CouponDO couponDO, Long userId) {
        //库存是否够
        if (couponDO.getStock() < 0) {
            throw new BizException(BizCodeEnum.COUPON_NO_STOCK);

        }
        //是否在领取范围
        Long time = CommonUtil.CurrentTimestamp();
        long startTime = couponDO.getStartTime().getTime();
        long endTime = couponDO.getEndTime().getTime();
        if (startTime > time && endTime < time) {
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_TIME);
        }
        //用户是否超过限制
        Integer recordNum = couponRecordMapper.selectCount(new QueryWrapper<CouponRecordDO>()
                .eq("coupon_id", couponDO.getId())
                .eq("user_id", userId)
        );
        if (recordNum >= couponDO.getUserLimit()) {
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT);
        }
    }

    private CouponVO beanProcess(CouponDO couponDO) {
        //类型转换
        CouponVO couponVO = new CouponVO();
        BeanUtils.copyProperties(couponDO, couponVO);
        return couponVO;
    }
}
